<?php
namespace App\Common\Cla\EChat;

use App\Common as com;
use App\Common\Cla\EChat\Room as thisRoom;
use App\Common\Cla\EChat as nameEChat;
use PhpAmqpLib\Exchange\AMQPExchangeType;

class Room extends com\Cla\ClaBase {

	private com\Cla\EChat $eChat;

	private string $channelSid;

	private string $roomSid;

	private string|bool|null $cloneRoomNo = null;

	private int $receiveIdIndex = 0;

	private array $receiveCallbackInfos = [];

	/**
	 * @var com\Lib\Com\RabbitMQ\_Channel
	 */
	private com\Lib\Com\RabbitMQ\_Channel $channelAsSend;

	/**
	 * @var com\Lib\Com\RabbitMQ\_Channel
	 */
	private com\Lib\Com\RabbitMQ\_Channel $channelAsReceive;

	/**
	 * @var com\Lib\Com\RabbitMQ\_Channel\_Exchange
	 */
	private com\Lib\Com\RabbitMQ\_Channel\_Exchange $exchange;

	/**
	 * 格式：
	 * @var com\Lib\Com\RabbitMQ\_Channel\_Queue
	 */
	private com\Lib\Com\RabbitMQ\_Channel\_Queue $queue;

	/**
	 * 最后发送时间
	 * @var int|null
	 */
	private int|null $lastSendTime = null;

	private thisRoom\ReceivePools $receivePools;

	/**
	 * 默认的接收超时时间
	 * @var int
	 */
	private int $defaultReceiveTimeoutS = 30;



	/**
	 * @var \WeakReference[]
	 */
	private array $needReplyMsgPoolOfWeakReference = [];

	/**
	 * Room constructor.
	 * @param com\Cla\EChat $eChat
	 * @param string|null $roomSid [null]随机创建
	 * @params array $params 参数数组
	 * ················· [可空](string)	$params['channelSid']	说明：通道id。[null][默认]使用默认通道
	 * ················· [可空](string|bool)	$params['cloneRoomNo']	说明：设置克隆房编号。[true]开启一个随机名称的克隆房 ['{str}']指定克隆房编号 [null][默认]不使用
	 * ················· [可空/多选_A/必须](unknown_type)	$params['AAAAA']	说明：AAAAA
	 */
	public function __construct( com\Cla\EChat $eChat, ?string $roomSid = null, array $params = [] ) {
		//
		$this->eChat = $eChat;
		$this->channelSid = $params['channelSid'] ?? $eChat->getChannelSid();
		$this->roomSid = $roomSid ?? self::buildRandomRoomSid();
		//
		// $this->receivePools = thisRoom\ReceivePools::makeOfNew( $this );
		$this->receivePools = thisRoom\ReceivePools::makeOfNew( \WeakReference::create( $this ) );
		// $this->goWeakReferencePool()->create( 'receivePools', thisRoom\ReceivePools::makeOfNew( $this ) );
		//
		$this->setCloneRoomNo( $params['cloneRoomNo'] ?? null );
		//
		parent::__construct();
	}

	/**
	 * @return thisRoom\ReceivePools
	 */
	/*
	public function getReceivePools(  ):thisRoom\ReceivePools {
		return $this->goWeakReferencePool()->get( 'receivePools' );
	}
	*/


	public function getReceivePools(  ):thisRoom\ReceivePools {
		return  $this->receivePools;
	}


	public function __destruct(  ) {
		$this->exitRoom();
		//
		parent::__destruct();
	}

	public function getEChat(  ):com\Cla\EChat {
		return $this->eChat;
	}

	public function getChannelSid(  ):string {
		return $this->channelSid;
	}

	/**
	 * 设置为克隆房(注意：队列初始化后不可调用)
	 * @param string|bool 设置克隆房编号。[true][默认]开启一个随机名称的克隆房 ['{str}']指定克隆房编号 [null]不使用
	 * @return $this
	 * @throws
	 */
	public function setCloneRoomNo( string|bool|null $cloneNo = true ):self {
		if( isset( $this->queue ) ){
			throw com\Lib\Err\Exception::makeOfNew( '队列初始化后不可调用', com\Lib\Err\Code::$sysAssertError );
		}
		//
		$this->cloneRoomNo = $cloneNo;
		//
		return $this;
	}

	/**
	 * 获取带前缀的ChannelSid
	 * @return string
	 */
	public function getChannelSidAsFullPrefix(  ):string {
		return nameEChat\Server::makeOfStatic()->getChannelSidPrefix() . $this->channelSid;
	}

	public function getRoomSid(  ):string {
		return $this->roomSid;
	}

	/**
	 * 获取房间sid（附带closeNo）
	 * @return string
	 */
	public function getRoomSidAsCloneRoomNo(  ):string {
		if( $this->cloneRoomNo !== null ){
			if( $this->cloneRoomNo === true ){
				return $this->roomSid . '#' . com\Lib\Fun\Str::uuid( false );
			}else{
				return $this->roomSid . '#' . $this->cloneRoomNo;
			}
		}else{
			return $this->roomSid;
		}
	}

	/**
	 * @return com\Lib\Com\RabbitMQ\_Channel
	 */
	public function getChannelAsSend(  ):com\Lib\Com\RabbitMQ\_Channel {
		if( ! isset( $this->channelAsSend ) ){
			$this->channelAsSend = $this->eChat->goRabbitMQ()->newChannel();
		}
		//
		return $this->channelAsSend;
	}

	/**
	 * @return com\Lib\Com\RabbitMQ\_Channel
	 */
	public function getChannelAsReceive(  ):com\Lib\Com\RabbitMQ\_Channel {
		if( ! isset( $this->channelAsReceive ) ){
			$this->channelAsReceive = $this->eChat->goRabbitMQ()->newChannel();
		}
		//
		return $this->channelAsReceive;
	}

	/**
	 * 获取默认的超时时间
	 * @return int
	 */
	public function getDefaultReceiveTimeoutS(  ):int {
		return $this->defaultReceiveTimeoutS;
	}

	/**
	 * 获取房间交换机
	 * @param bool $passive
	 * @param bool $durable
	 * @param bool $autoDelete
	 * @return com\Lib\Com\RabbitMQ\_Channel\_Exchange
	 */
	public function getRoomExchange( bool $passive = false, bool $durable = false, bool $autoDelete = true ):com\Lib\Com\RabbitMQ\_Channel\_Exchange {
		if( ! isset( $this->exchange ) ){
			$this->roomExchangeDeclare( $passive, $durable, $autoDelete );
		}
		//
		return $this->exchange;
	}

	/**
	 * 声明房间交换机
	 * @param bool $passive
	 * @param bool $durable
	 * @param bool $autoDelete
	 * @return $this
	 */
	public function roomExchangeDeclare( bool $passive = false, bool $durable = false, bool $autoDelete = true ):self {
		if( ! isset( $this->exchange ) ){
			$this->exchange = $this->getChannelAsReceive()->newExchange( $this->eChat->getChannelSidPrefix() . $this->getChannelSid() );
			$this->exchange->exchangeDeclare( AMQPExchangeType::TOPIC, $passive, $durable, $autoDelete );
		}else{
			throw com\Lib\Err\Exception::makeOfNew( '已声明交换机', com\Lib\Err\Code::$sysAssertError );
		}
		//
		return $this;
	}

	/**
	 * 是否存在房间队列
	 * @return bool
	 */
	public function hasRoomQueue():bool {
		return isset( $this->queue );
	}

	/**
	 * 获取房间队列
	 * @param bool $passive
	 * @param bool $durable
	 * @param bool $exclusive
	 * @param bool $autoDelete
	 * @return com\Lib\Com\RabbitMQ\_Channel\_Queue
	 */
	public function getRoomQueue( bool $passive = false, bool $durable = false, bool $exclusive = false, bool $autoDelete = true ):com\Lib\Com\RabbitMQ\_Channel\_Queue {
		if( ! isset( $this->queue ) ){
			$this->roomQueueDeclare( $passive, $durable, $exclusive, $autoDelete );
		}
		//
		return $this->queue;
	}

	/**
	 * 声明房间队列
	 * @param bool $passive
	 * @param bool $durable
	 * @param bool $exclusive
	 * @param bool $autoDelete
	 * @return $this
	 */
	public function roomQueueDeclare( bool $passive = false, bool $durable = false, bool $exclusive = false, bool $autoDelete = true ):self {
		if( ! isset( $this->queue ) ){
			$exchangeNameAsPrefix = $this->eChat->getChannelSidPrefix() . $this->getChannelSid();
			// 初始化交换机
			if( ! isset( $this->exchange ) ){
				$this->roomExchangeDeclare();
			}
			//
			$queueName = "{$exchangeNameAsPrefix}->" . 'room.msg.' . $this->getRoomSidAsCloneRoomNo();
			// 接收消息
			$this->queue = $this->getChannelAsReceive()->newQueue( $queueName );
			$this->queue->declareQueue( $passive, $durable, $exclusive, $autoDelete )
				->bind( $exchangeNameAsPrefix, 'room.msg.' . $this->getRoomSid() );
			/*
			d([
				'roomQueueDeclare',
				$queueName,
				'room.msg.' . $this->getRoomSid(),
			]);
			*/
		}else{
			throw com\Lib\Err\Exception::makeOfNew( '已声明队列', com\Lib\Err\Code::$sysAssertError );
		}

		//
		return $this;
	}

	/**
	 * 获取房间的mailId（注意：克隆房也会接收到发送给该mailId的消息）
	 * @param string|null $privateId 如果$this->mailId里数据，本参数不生效
	 * @return string
	 */
	public function getRoomMailId( string $privateId = null ):string {
		$mail = $this->getChannelSid() . '@' . $this->getRoomSid();
		if( $privateId !== null ){
			$mail .= '@' . $privateId;
		}
		//
		return $mail;
	}

	/**
	 * 获取最后发送时间（调用$this->send()的时间）
	 * @return int|null
	 */
	public function getLastSendTime(  ):int|null {
		return $this->lastSendTime;
	}

	/**
	 * 检查房间是否在线
	 * @return bool
	 */
	public function isOnline(  ):bool {
		$res = $this->sendOfReply( [
			'api' => 'ping'
		], 5, [
			'msgType' => thisRoom\Msg::$varMsgType_Sys,
		] );
		//
		return $res !== null;
	}

	/**
	 * 发送
	 * @param array|string|int|float $data
	 * @param array $headers
	 * @return bool
	 */
	public function send( array|string|int|float $data, array $headers = [] ):bool {
		$packet = [
			'body' => $data,
			'headers' => $headers,
		];
		// 检查toMailId
		if( isset( $packet['headers']['toMailId'] ) ){
			// 检查mailId是否完整，如果不完整则补充完整
			if( strstr( $packet['headers']['toMailId'], '@' ) === false ){
				$packet['headers']['toMailId'] = $this->getRoomMailId( $packet['headers']['toMailId'] );
			}
		}
		// 初始化房间
		$this->getRoomQueue();
		/*
		d([
			'send',
			'publish' => "room.msg." . $this->getRoomSidAsCloneRoomNo(),
			'data' => $data,
		]);
		*/
		//
		$this->getRoomExchange()->publish( json_encode( $packet, JSON_UNESCAPED_UNICODE ), "room.msg." . $this->getRoomSidAsCloneRoomNo() );
		//
		$this->lastSendTime = com\Lib\Fun\Time::getTime();
		//
		return true;
	}

	/**
	 * 发送(toMailId)
	 * @param string $toMailId
	 * @param array|string|int $data
	 * @param array $headers
	 * @return bool
	 */
	public function sendAsToMail( string $toMailId, $data, array $headers = [] ):bool {
		$headers['toMailId'] = $toMailId;
		//
		return $this->send( $data, $headers );
	}

	/**
	 * 发送并返回回复数据
	 * @param array|string|int|float $data
	 * @param int|null $receiveTimeoutS [null]永久
	 * @param array $headers
	 * @param \Closure|null $resMatchFn 自定义结果匹配函数(函数签名：fn( com\Cla\EChat\Room\Msg $chatMsg ):mixed，[!false|!null]匹配成功并立即退出 [false]匹配失败并立即退出 [null]未匹配，继续) [null][默认]系统处理
	 * @return mixed [null]失败
	 */
	public function sendOfReply( array|string|int|float $data, ?int $receiveTimeoutS = 30, array $headers = [], \Closure|null $resMatchFn = null ):mixed {
		$replyRoomObj = $this->getEChat()->joinRoom();
		// $replyRoomObj = com\Cla\EChat::makeOfNew( 'ztnIot' )->joinRoom();
/*
		d([
			'开启一个回复房间',
			'mailId' => $replyRoomObj->getRoomMailId(),
			'目标房间' => $this->getRoomMailId(),
		]);
*/
		//
		$dataChannel = new \Swoole\Coroutine\Channel();
		$replyRoomObj->receiveOfCoroutine( function( com\Cla\EChat\Room\Msg $chatMsg ) use ( $replyRoomObj, $dataChannel, $resMatchFn ){
			/*
			d([
				'回复房间收到消息',
				$chatMsg->getMsgBody()
			]);
			*/
			//
			if( $resMatchFn !== null ){
				$resMatchFnRes = call_user_func_array( $resMatchFn, [ $chatMsg ] );
				if( $resMatchFnRes === null ){
					/// 未匹配，继续
					return null;
				}else if( $resMatchFnRes === false ){
					// 匹配失败并立即退出
					$dataChannel->push( null );
				}else{
					// 匹配成功并立即退出
					$dataChannel->push( $chatMsg->getMsgBody() );
				}
			}else{
				$dataChannel->push( $chatMsg->getMsgBody() );
			}
		}, $receiveTimeoutS );
		//
		$headers['fromMailId'] = $replyRoomObj->getRoomMailId();
		// 设置回复类型
		$headers['replyType'] = thisRoom\Msg::$varReplyType_Single;
		$headers['replyTimeout'] = $receiveTimeoutS;
		//
		/*
		d([
			'sendOfReply',
			$data,
			$headers,
		]);
		*/
		$this->send( $data, $headers );
		//
		$res = $dataChannel->pop( $receiveTimeoutS );
		// $replyRoomObj->waitReceive();
		//
		$replyRoomObj->exitRoom();
		// 广播回复房间解散通知
		$this->broadcastAsCloseReplyRoom( $headers['fromMailId'] );
		/*
		d([
			'exitRoom2',
			$res,
		]);
		*/
		//
		return $res !== false ? $res : null;
	}

	/**
	 * 广播回复房间关闭通知
	 * @param string $replyRoomMailId
	 * @return bool
	 */
	public function broadcastAsCloseReplyRoom( string $replyRoomMailId ):bool {
		$data = [
			'api' => 'closeReplyRoom',
			'param' => [
				'replyRoomMailId' =>$replyRoomMailId
			],
		];
		$headers = [
			'msgType' => thisRoom\Msg::$varMsgType_Sys,
		];
		//
		return $this->send( $data, $headers );
	}

	/**
	 * 发送并返回回复数据（返回数组）
	 * @param $data
	 * @param int|null $receiveTimeoutS [null]永久
	 * @param array $headers
	 * @return array|null [null]失败
	 */
	public function sendOfReplyReArray( $data, ?int $receiveTimeoutS = 30, array $headers = [] ):?array {
		return json_decode( $this->sendOfReply( $data, $receiveTimeoutS, $headers ), true );
	}

	/**
	 * 发送并等待回复(回调)
	 * @param $data
	 * @param \Closure $replyCallback
	 * @param int|null $receiveTimeoutS
	 * @param array $headers
	 * @return bool
	 */
	public function sendOfReplyFn( $data, \Closure $replyCallback, ?int $receiveTimeoutS = 30, array $headers = [] ):bool {
		//
		$replyRoomObj = $this->getEChat()->joinRoom();
		//
		$replyRoomObj->receiveOfCoroutine( function( com\Cla\EChat\Room\Msg $chatMsg ) use ( $replyRoomObj, $replyCallback ){
			if( $chatMsg->getReplyStatusOrNull() === $chatMsg::$varHeaderReplyStatus_End ){
				$replyRoomObj->stopReceives();
			}
			//
			call_user_func( $replyCallback, $chatMsg );
		}, $receiveTimeoutS );
		//
		$headers['replyRoomSid'] = $replyRoomObj->getRoomSid();
		$this->send( $data, $headers );
		//
		return true;
	}

	/**
	 * 接收消息
	 * @param \Closure $callback fn( com\Cla\EChat\Room\Msg $chatMsg ):midex [非null]删除这个接收器，并返回返回的数据 [null]继续执行
	 * @param int|null $receiveTimeoutS 接收超时（单位：秒） [null]默认超时 [-1][默认]不超时
	 * @return thisRoom\ReceivePools\Item 接收对象
	 */
	public function receiveOfCoroutine( \Closure $callback, ?int $receiveTimeoutS = -1 ):thisRoom\ReceivePools\Item {
		$receiveTimeoutS ??= $this->defaultReceiveTimeoutS;
		// 初始化交换机
		$queue = $this->getRoomQueue();
		//
		$receiveObj = $this->receivePools->add( $callback, $receiveTimeoutS );
		// 运行接收器
		if( ! $queue->goConsume()->isRunStatus() ){
			$queue->goConsume()->callback( function ( \PhpAmqpLib\Message\AMQPMessage $AMQPMsg ) use ( $queue ) {
				go( function () use ( $AMQPMsg, $queue ){
					$this->consumeHandle( $AMQPMsg );
					//
					if( $queue->getChannel()->is_open() ){
						$AMQPMsg->ack();
					}
				} );
			} )->runOfCoroutine();
		}
		//
		return $receiveObj;
	}

	/**
	 * 消息处理器
	 * @param \PhpAmqpLib\Message\AMQPMessage $msg
	 */
	private function consumeHandle( \PhpAmqpLib\Message\AMQPMessage $AMQPMsg ):void {
		$this->receivePools->cg();
		// 提前转为数据
		$msgObj = com\Cla\EChat\Room\Msg::makeOfNew( $this, $AMQPMsg );
/*		d([
			'body' => $msgObj->getMsgBody(),
			'type' => $msgObj->getMsgType(),
			'headers' => $msgObj->getHeaders(),
		]);*/
		// 检查消息类型
		switch( $msgObj->getMsgType() ){
			case thisRoom\Msg::$varMsgType_Sys:
				$this->consumeHandleAsSys( $AMQPMsg, $msgObj );
				break;
			case thisRoom\Msg::$varMsgType_Msg:
				$this->consumeHandleAsMsg( $AMQPMsg, $msgObj );
				break;
			default:
				throw com\Lib\Err\Exception::makeOfNew( '', com\Lib\Err\Code::$sysNonsupportType );
		}
	}

	/**
	 * @param \PhpAmqpLib\Message\AMQPMessage $AMQPMsg
	 * @param Room\Msg $msgObj
	 * @return void
	 * @throws com\Lib\Err\BaseException\Throw_
	 */
	private function consumeHandleAsSys( \PhpAmqpLib\Message\AMQPMessage $AMQPMsg, com\Cla\EChat\Room\Msg $msgObj ):void {
		$api = $msgObj->getMsgBody( 'api' );
		switch( $api ){
			case 'ping':
				$msgObj->replyEnd( 'pong' );
				break;
			case 'closeReplyRoom':
				// 获取回复房间的mailId
				$replyRoomMailId = $msgObj->getMsgBody( 'param->replyRoomMailId' );
				// 关闭回复
				if( isset( $this->needReplyMsgPoolOfWeakReference[ $replyRoomMailId ] ) ){
					/** @var com\Cla\EChat\Room\Msg $replyMsgObj */
					$replyMsgObj = $this->needReplyMsgPoolOfWeakReference[ $replyRoomMailId ]->get();
					if( $replyMsgObj !== null ){
						$replyMsgObj->replyExit();
					}else{
						$this->cgAsNeedReplyMsgPoolOfWeakReference( $replyRoomMailId );
					}
				}
				break;
		}
	}

	/**
	 * @param \PhpAmqpLib\Message\AMQPMessage $AMQPMsg
	 * @param Room\Msg $msgObj
	 * @return void
	 */
	private function consumeHandleAsMsg( \PhpAmqpLib\Message\AMQPMessage $AMQPMsg, com\Cla\EChat\Room\Msg $msgObj ):void {
		//
		// 检查是否拥有回复房间
		if( $msgObj->isNeedReply() ){
			$fromMailId = $msgObj->getFromMailIdOrNull();
			if( $fromMailId !== null ){
				$this->needReplyMsgPoolOfWeakReference[ $msgObj->getFromMailIdOrNull() ] = \WeakReference::create( $msgObj );
			}
			// 触发垃圾清理
			$this->cgAsNeedReplyMsgPoolOfWeakReference();
		}
		//
		foreach( $this->receivePools->getReceiveItemPools() as $receiveItem ){
			try{
				$re = call_user_func_array( $receiveItem->getCallback(), [ $msgObj ] );
			}catch( \Throwable $e ){
				com\Lib\Err\Exception::showSimpleShowExceptionInfo( $e );
				$re = null;
			}
			if( $re !== null ){
				$receiveItem->stopReceive( $re );
			}
		}
		//
		$this->receivePools->ifReceiveEmptyThatStop();
	}

	/**
	 * 垃圾清理->needReplyMsgPoolOfWeakReference
	 * @param string|null $replyRoomMailId 删除指定的数据（不触发其他数据的cg）
	 * @return bool
	 */
	public function cgAsNeedReplyMsgPoolOfWeakReference( string|null $replyRoomMailId = null ):bool {
		// 删除指定的数据
		if( $replyRoomMailId !== null ){
			unset( $this->needReplyMsgPoolOfWeakReference[ $replyRoomMailId ] );
			//
			return true;
		}
		// 10%执行概率
		if( com\Lib\Fun\Num::randInt( 1, 10 ) !== 1 ){
			return false;
		}
		//
		$currentTime = com\Lib\Fun\Time::getTime();
		foreach( $this->needReplyMsgPoolOfWeakReference as $needReplyMsgOfWeakReferenceKey => $needReplyMsgOfWeakReference ){
			/** @var com\Cla\EChat\Room\Msg|null $msgObj */
			$msgObj = $needReplyMsgOfWeakReference->get();
			if( $msgObj === null ){
				unset( $this->needReplyMsgPoolOfWeakReference[ $needReplyMsgOfWeakReferenceKey ] );
			}else if( $currentTime > ( $msgObj->getMsgReceiveTime() + $msgObj->getHeaders( 'replyTimeout', 0 ) ) ){
				// 清理超时
				unset( $this->needReplyMsgPoolOfWeakReference[ $needReplyMsgOfWeakReferenceKey ] );
			}
		}
		return true;
	}

	/**
	 * 接收消息并等待事件结束
	 * @param \Closure $callback fn( com\Cla\EChat\Room\Msg $chatMsg, com\Cla\EChat\Server\ReceivePools\Item $receiveItem ):null 不处理任何返回数据
	 * @param int|null $receiveTimeoutS 接收超时（单位：秒） [null]默认超时 [-1][默认]不超时
	 * @return mixed 是否已结束 [其他]正常结束，并返回fn的返回数据 [fasle]超时或已关闭
	 */
	public function receiveOfCoroutineAndWait( \Closure $callback, ?int $receiveTimeoutS = -1 ):mixed {
		$receiveTimeoutS ??= $this->defaultReceiveTimeoutS;
		//
		$receiveObj = $this->receiveOfCoroutine( $callback, $receiveTimeoutS );
		return $receiveObj->waitReceive( $receiveTimeoutS );
	}

	/**
	 * 接收消息、触发事件并等待。
	 * @param \Closure $callback fn( com\Cla\EChat\Room\Msg $chatMsg ):midex 回调函数。可以调用$chatMsg->getReceiveItem()->stopReceive()手动结束 [非null]删除这个接收器，并返回返回的数据 [null]继续执行
	 * @param \Closure $receiveLaterCallback 开启接收后的回调 fn( com\Cla\EChat\Room\ReceivePools\Item ):null 不处理任何返回数据。[false]自动停止接收器
	 * @param int|null $receiveTimeoutS 接收超时（单位：秒） [null]默认超时 [-1][默认]不超时
	 * @return mixed 是否已结束 [其他]正常结束，并返回fn的返回数据 [fasle]超时或已关闭
	 */
	public function receiveOfCoroutineThatFnThatWait( \Closure $callback, \Closure $receiveLaterCallback, ?int $receiveTimeoutS = -1 ):mixed {
		$receiveTimeoutS ??= $this->defaultReceiveTimeoutS;
		//
		$receiveObj = $this->receiveOfCoroutine( $callback, $receiveTimeoutS );
		// 开启接收后的回调
		if( call_user_func_array( $receiveLaterCallback, [ $receiveObj ] ) === false ){
			$receiveObj->stopReceive();
		}
		//
		return $receiveObj->waitReceive( $receiveTimeoutS );
	}

	/**
	 * @param int $receiveId
	 * @param int|null $waitTimeoutS 等待超时时间。单位：秒
	 * @return mixed 是否已结束 [其他]正常结束，并返回fn的返回数据 [fasle]超时或已关闭
	 */
	public function waitReceive( int $receiveId, ?int $waitTimeoutS = null ):mixed {
		return $this->receivePools->waitReceive( $receiveId, $waitTimeoutS );
	}

	/**
	 * 停止接收消息
	 * @param int $receiveId 指定id
	 * @param mixed $reData 结束通知数据
	 * @return bool [true][必返]
	 */
	public function stopReceive( int $receiveId, mixed $reData = null ):bool {
		return $this->receivePools->stopReceive( $receiveId, $reData );
	}

	/**
	 * 停止接收消息(多个)
	 * @param array|null $receiveIds 指定id [null][默认]全部
	 * @param mixed $reData 结束通知数据
	 * @return bool [true][必返]
	 */
	public function stopReceives( ?array $receiveIds = null, mixed $reData = null ):bool {
		return $this->receivePools->stopReceives( $receiveIds, $reData );
	}

	/**
	 * 退出房间
	 * @return bool [true][必返]
	 */
	public function exitRoom():bool {
		$this->stopReceives();
		//
		if( $this->hasRoomQueue() ){
			// d('->1');
			$this->getRoomQueue()->goConsume()->stopAndWait();
		}
		//
		if( $this->getChannelAsReceive()->getChannel()->is_open() ){
			$this->getChannelAsReceive()->getChannel()->close();
		}
		//
		return true;
	}
	
	// ===============================
	//
	//
	// 下面为静态方法
	//
	//
	// ===============================

	/**
	 * 创建随机房间sid
	 * @return string
	 */
	public static function buildRandomRoomSid(  ):string {
		return com\Lib\Fun\Str::uuidKeyGen();
	}

	/**
	 * @param com\Cla\EChat $eChat
	 * @param string|null $roomSid [null]随机创建
	 * @params array $params 参数数组
	 * ················· [可空](string)	$params['channelSid']	说明：通道id。[null][默认]使用默认通道
	 * ················· [可空/多选_A/必须](unknown_type)	$params['AAAAA']	说明：AAAAA
	 * @return $this
	 */
	public static function makeOfNew( com\Cla\EChat $eChat, ?string $roomSid = null, array $params ):self {
		return self::make( $eChat, $roomSid, $params );
	}

}
